<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="CPU访问内存：内存分段与内存分页, python,machine learning,deep learning,html,css,c,c++,cpp,cmake,ros,linux,ubuntu">
    <meta name="description" content="本文介绍了CPU访问内存的两种方式：内存分段式访问与内存分页式访问。此外还讲解了包括物理地址、逻辑地址、有效地址和虚拟地址等等在内的概念">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="referrer" content="no-referrer-when-downgrade">
    <!-- Global site tag (gtag.js) - Google Analytics -->


    <title>CPU访问内存：内存分段与内存分页 | JackWang&#39;s Blog</title>
    <link rel="icon" type="image/png" href="/favicon.png">

    <link rel="stylesheet" type="text/css" href="/libs/awesome/css/all.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="/css/matery.css">
    <link rel="stylesheet" type="text/css" href="/css/my.css">

    <script src="/libs/jquery/jquery-3.6.0.min.js"></script>

<meta name="generator" content="Hexo 5.4.2">
<style>.github-emoji { position: relative; display: inline-block; width: 1.2em; min-height: 1.2em; overflow: hidden; vertical-align: top; color: transparent; }  .github-emoji > span { position: relative; z-index: 10; }  .github-emoji img, .github-emoji .fancybox { margin: 0 !important; padding: 0 !important; border: none !important; outline: none !important; text-decoration: none !important; user-select: none !important; cursor: auto !important; }  .github-emoji img { height: 1.2em !important; width: 1.2em !important; position: absolute !important; left: 50% !important; top: 50% !important; transform: translate(-50%, -50%) !important; user-select: none !important; cursor: auto !important; } .github-emoji-fallback { color: inherit; } .github-emoji-fallback img { opacity: 0 !important; }</style>
<link rel="stylesheet" href="/css/prism-tomorrow.css" type="text/css">
<link rel="stylesheet" href="/css/prism-line-numbers.css" type="text/css"></head>



   <style>
    body{
       background-image: url(https://cdn.jsdelivr.net/gh/Tokisaki-Galaxy/res/site/medias/background.jpg);
       background-repeat:no-repeat;
       background-size: 100% 100%;
       background-attachment:fixed;
    }
</style>



<body>
    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/" class="waves-effect waves-light">
                    
                    <img src="/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">JackWang&#39;s Blog</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="" class="waves-effect waves-light">

      
      <i class="fas fa-book-reader" style="zoom: 0.6;"></i>
      
      <span>博客</span>
      <i class="fas fa-chevron-down" aria-hidden="true" style="zoom: 0.6;"></i>
    </a>
    <ul class="sub-nav menus_item_child ">
      
      <li>
        <a href="/tags">
          
          <i class="fas fa-tags" style="margin-top: -20px; zoom: 0.6;"></i>
          
	  <span>按标签归类文章</span>
        </a>
      </li>
      
      <li>
        <a href="/categories">
          
          <i class="fas fa-bookmark" style="margin-top: -20px; zoom: 0.6;"></i>
          
	  <span>按目录归类文章</span>
        </a>
      </li>
      
      <li>
        <a href="/archives">
          
          <i class="fas fa-archive" style="margin-top: -20px; zoom: 0.6;"></i>
          
	  <span>按日期分类文章</span>
        </a>
      </li>
      
    </ul>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/about" class="waves-effect waves-light">
      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>



<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">JackWang&#39;s Blog</div>
        <div class="logo-desc">
            
            JackWang的个人博客
            
        </div>
    </div>

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="javascript:;">
			
				<i class="fa-fw fas fa-book-reader"></i>
			
			博客
			<span class="m-icon"><i class="fas fa-chevron-right"></i></span>
		</a>
            <ul  style="background:  ;" >
              
                <li>

                  <a href="/tags " style="margin-left:75px">
				  
				   <i class="fa fas fa-tags" style="position: absolute;left:50px" ></i>
			      
                              <span>按标签归类文章</    span>

                  </a>
                </li>
              
                <li>

                  <a href="/categories " style="margin-left:75px">
				  
				   <i class="fa fas fa-bookmark" style="position: absolute;left:50px" ></i>
			      
                              <span>按目录归类文章</    span>

                  </a>
                </li>
              
                <li>

                  <a href="/archives " style="margin-left:75px">
				  
				   <i class="fa fas fa-archive" style="position: absolute;left:50px" ></i>
			      
                              <span>按日期分类文章</    span>

                  </a>
                </li>
              
            </ul>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/about" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-user-circle"></i>
			
			关于
		</a>
          
        </li>
        
        
    </ul>
</div>


        </div>

        
    </nav>

</header>

    
<script src="/libs/cryptojs/crypto-js.min.js"></script>
<script>
    (function() {
        let pwd = '';
        if (pwd && pwd.length > 0) {
            if (pwd !== CryptoJS.SHA256(prompt('抱歉，这篇文章并不想让所有人都看到，请输入授权密码观看')).toString(CryptoJS.enc.Hex)) {
                alert('密码错误，将返回主页！');
                location.href = '/';
            }
        }
    })();
</script>




<div class="bg-cover pd-header post-cover" style="background-image: url('https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117233832116.png')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 class="description center-align post-title">CPU访问内存：内存分段与内存分页</h1>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        width: 345px;
        padding-left: 20px;
    }

    .toc-widget .toc-title {
        padding: 35px 0 15px 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content {
        padding-bottom: 30px;
        overflow: auto;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;

        position: absolute;
        right: 23.5vw;
        display: block;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                    <div class="article-tag">
                        
                            <a href="/tags/%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80/">
                                <span class="chip bg-color">物理地址</span>
                            </a>
                        
                            <a href="/tags/%E9%80%BB%E8%BE%91%E5%9C%B0%E5%9D%80/">
                                <span class="chip bg-color">逻辑地址</span>
                            </a>
                        
                            <a href="/tags/%E6%9C%89%E6%95%88%E5%9C%B0%E5%9D%80/">
                                <span class="chip bg-color">有效地址</span>
                            </a>
                        
                            <a href="/tags/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80/">
                                <span class="chip bg-color">线性地址</span>
                            </a>
                        
                            <a href="/tags/%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80/">
                                <span class="chip bg-color">虚拟地址</span>
                            </a>
                        
                            <a href="/tags/%E5%AE%9E%E6%A8%A1%E5%BC%8F/">
                                <span class="chip bg-color">实模式</span>
                            </a>
                        
                            <a href="/tags/%E4%BF%9D%E6%8A%A4%E6%A8%A1%E5%BC%8F/">
                                <span class="chip bg-color">保护模式</span>
                            </a>
                        
                            <a href="/tags/%E6%AE%B5%E9%80%89%E6%8B%A9%E5%AD%90/">
                                <span class="chip bg-color">段选择子</span>
                            </a>
                        
                            <a href="/tags/%E6%AE%B5%E6%8F%8F%E8%BF%B0%E7%AC%A6%E8%A1%A8/">
                                <span class="chip bg-color">段描述符表</span>
                            </a>
                        
                            <a href="/tags/%E7%9F%AD%E6%8F%8F%E8%BF%B0%E7%AC%A6/">
                                <span class="chip bg-color">短描述符</span>
                            </a>
                        
                            <a href="/tags/%E5%86%85%E5%AD%98%E5%88%86%E6%AE%B5/">
                                <span class="chip bg-color">内存分段</span>
                            </a>
                        
                            <a href="/tags/%E5%86%85%E5%AD%98%E5%88%86%E9%A1%B5/">
                                <span class="chip bg-color">内存分页</span>
                            </a>
                        
                            <a href="/tags/%E9%A1%B5%E8%A1%A8/">
                                <span class="chip bg-color">页表</span>
                            </a>
                        
                            <a href="/tags/%E9%A1%B5%E8%A1%A8%E9%A1%B9/">
                                <span class="chip bg-color">页表项</span>
                            </a>
                        
                    </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/" class="post-category">
                                操作系统
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2022-11-15
                </div>
                

                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-check fa-fw"></i>更新日期:&nbsp;&nbsp;
                    2023-06-01
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-file-word fa-fw"></i>文章字数:&nbsp;&nbsp;
                    14.2k
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-clock fa-fw"></i>阅读时长:&nbsp;&nbsp;
                    49 分
                </div>
                

                
                    <div id="busuanzi_container_page_pv" class="info-break-policy">
                        <i class="far fa-eye fa-fw"></i>阅读次数:&nbsp;&nbsp;
                        <span id="busuanzi_value_page_pv"></span>
                    </div>
				
            </div>
        </div>
        <hr class="clearfix">

        

        

        <div class="card-content article-card-content">
            <div id="articleContent">
                <p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117233832116.png" alt="实模式与保护模式访问内存"></p>
<blockquote>
<p>这张图，包含了操作系统访问内存的所有内容，看懂这张图，就看明白了操作系统是如何访问内存的。</p>
</blockquote>
<h1 id="CPU访问内存：内存分段与内存分页"><a href="#CPU访问内存：内存分段与内存分页" class="headerlink" title="CPU访问内存：内存分段与内存分页"></a>CPU访问内存：内存分段与内存分页</h1><h2 id="一、各种地址"><a href="#一、各种地址" class="headerlink" title="一、各种地址"></a>一、各种地址</h2><p>CPU访问物理内存的过程中有很多的地址，包括：<code>物理地址</code>、<code>逻辑地址</code>、<code>有效地址</code>、<code>线性地址</code>、<code>虚拟地址</code>……</p>
<p>所以在介绍CPU访问内存的方式前（也是为了后面的复习），先介绍一下各种地址。</p>
<h3 id="1-物理地址"><a href="#1-物理地址" class="headerlink" title="1. 物理地址"></a>1. 物理地址</h3><p><strong><code>物理地址</code>就是物理内存真正的地址，相当于内存中每个存储单元的门牌号，具有唯一性。不管在什么模式下，只有通过<code>物理地址</code>去访问内存，才能够获得数据。什么<code>虚拟地址</code>、<code>线性地址</code>，最终都要转换为<code>物理地址</code>，然后再去访问内存</strong>。</p>
<p>在实模式下，并不是直接直接给CPU<code>物理地址</code>，然后CPU去访问内存，而是通过<code>段基址＋段内偏移地址</code>，经过段部件的处理，得到物理地址，而后CPU再通过此地址访问内存。</p>
<h3 id="2-线性地址-虚拟地址"><a href="#2-线性地址-虚拟地址" class="headerlink" title="2. 线性地址/虚拟地址"></a>2. 线性地址/虚拟地址</h3><blockquote>
<p><code>线性地址</code>与<code>虚拟地址</code>本质上就是同一个东西。区别就在于是否打开了<code>分页机制</code>。</p>
</blockquote>
<p>在保护模式下，形式上CPU依然通过<code>段基址＋段内偏移地址</code>的方式去访问内存，但是此时<code>段基址+段内偏移</code>中段基址已经不再是实模式下真正的地址。而是个称为<code>选择子</code>的东西。</p>
<p><strong>选择子本质是个索引，即数组下标。通过这个索引便能在<code>全局段描述符表</code>（<code>Global Descriptor Table, GDT</code>）这个数组中找到相应的段描述符。段描述符中记录了该段的基地址、大小等信息，这样便得到了段基址</strong>。</p>
<p><strong>此时，在保护模式下，<code>段基地址+段内偏移</code>通过查GDT之后得到的地址称为线性地址</strong>。若没有开启地址分页功能，此<code>线性地址</code>就被作<code>物理地址</code>来用，可直接访问内存。</p>
<p><strong>若开启了分页功能，此<code>线性地址</code>又多了个名字，就是<code>虚拟地址</code>。虚拟地址、线性地址在分页机制下就是一个东西。 <code>虚拟地址</code>还要经过页部件转换成具体的<code>物理地址</code>，这样CPU才能将其送上地址总线去访问内存。</strong>也就是说，在开启了分页功能之后，线性地址还需要经过一次转换，才能得到物理地址。</p>
<p>由于分页功能是需要在保护模式下开启的，32位系统保护模式下的寻址空间是4GB ，所以<code>虚拟地址</code>或<code>线性地址</code>就能访问到4GB范围内的内存。</p>
<blockquote>
<p><strong><code>线性地址</code>或者说<code>虚拟地址</code>，都不是真实的内存地址。它们其实都是程序眼中的内存地址，因此描述程序或任务的地址空间</strong>。<strong><code>线性地址</code>由于未开启分页机制，因此此时程序眼中的地址空间和物理地址空间其实是一一对应的。而开启分页后，程序眼中的地址空间不和物理地址空间一一对应了，而是存在一个映射关系</strong></p>
<p>下图表示了这种关系，线性地址空间和最右侧的物理地址空间是一一对应的，而中间的虚拟地址空间和物理地址空间不是一一对应的。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116140419990.png" alt="线性地址空间、虚拟地址空间和物理地址空间" style="zoom:50%;"></p>
</blockquote>
<h3 id="3-有效地址-逻辑地址"><a href="#3-有效地址-逻辑地址" class="headerlink" title="3. 有效地址/逻辑地址"></a>3. 有效地址/逻辑地址</h3><p><strong>无论在实模式或是保护模式下，<code>段内偏移地址</code>又称为<code>有效地址</code>，也称为<code>逻辑地址</code>。事实上，<code>有效地址</code>或者说<code>逻辑地址</code>是程序员可见的地址，例如我们在C语言中指针存储的地址的值，其实就是有效地址。</strong></p>
<p>这是因为，虽然说最终的<code>物理地址</code>是需要由<code>段基址+段内偏移</code>组合后经过转换而得到的，但是转换是针对CPU来说的，对于用户来说，我们只需要个给出<code>段基址</code>和<code>段内偏移</code>。由于<code>段基址</code>一般在编译时候已经有默认，而在运行的时候已经被指定，要么是在实模式下的默认段寄存器中，要么是在保护模式下的默认段选择子寄存器指向的段描述符中。所以我们用户其实只要给出<code>段内偏移</code>就行了，这个地址虽然只是段内偏移，但加上默认的段基址，就能够通过转换得到物理地址了。</p>
<h2 id="二、实模式下访问内存：内存分段"><a href="#二、实模式下访问内存：内存分段" class="headerlink" title="二、实模式下访问内存：内存分段"></a>二、实模式下访问内存：内存分段</h2><blockquote>
<p>说到实模式，就不得不说<code>Intel 8086 CPU</code>了。虽然说<code>Intel 8086</code>这款<code>CPU</code>在1978年上市的，但是作为最早，也是最成功的CPU，其中诸多标准都被后来的CPU中继承了下来，并且一直延续至今。实模式就是伴随<code>Intel 8086 CPU</code>一起提出的，因此我们下面讲一边讲解<code>Intel 8086 CPU</code>，一边讲解伴随<code>Intel 8086 CPU</code>所提出的实模式以及实模式下的内存访问方式。</p>
</blockquote>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116130929710.png" alt="Intel 8086 CPU" style="zoom:33%;"></p>
<h3 id="1-什么是实模式"><a href="#1-什么是实模式" class="headerlink" title="1. 什么是实模式"></a>1. 什么是实模式</h3><blockquote>
<p>说到内存分段式访问，就得先说<code>实模式</code></p>
</blockquote>
<p><code>CPU</code>中本来是没有实模式这一称呼的，而是随着<code>CPU</code>的发展，后面有了保护模式后，为了与老的模式区别开来，所以称老的模式为实模式。</p>
<p><code>实模式</code>的<strong>实</strong>体现在：<strong>程序中用到的地址都是真实的物理地址，即通过<code>段基地:段内偏移</code>得到的逻辑地址就是物理地址，也就是程序员看到的完全是真实的内存</strong></p>
<p>此外，所谓模式，指的就是<code>CPU</code>的运行方式，是包含<code>寻址方式</code>、<code>寄存器</code>、<code>指令集合</code>等等在内的所有内容。为此，为了讲解伴随<code>8086 CPU</code>诞生的实模式，我们下面先介绍<code>8086 CPU</code>的相关内容。</p>
<h3 id="2-8086的寄存器"><a href="#2-8086的寄存器" class="headerlink" title="2. 8086的寄存器"></a>2. 8086的寄存器</h3><p><strong>寄存器是一种物理存储元件，一般都是集成在<code>CPU</code>上的。因此，<code>CPU</code>访问寄存器并从中获取需要计算的数据所需要的时间比访问内存等等存储介质所需要的时间更短</strong></p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/layered-storage.png" alt="各种存储介质访问需要的时间以及容量" style="zoom:50%;"></p>
<p>一般的存储介质要快，因此能够跟上<code>CPU</code>的步伐，所以在<code>CPU</code>内部有很多寄存器用来给<code>CPU</code>存取数据</p>
<p><code>CPU</code>中的寄存器大致上分为两大类：</p>
<ul>
<li><strong>第一类寄存器是在<code>CPU</code>内部使用的，对程序员不可见</strong>。<strong>是否可见不是说寄存器是否能看得见，而是指程序员是否能使用这些寄存器</strong>。<code>CPU</code>内部有其运行机制，因此<code>CPU</code>中就会有一些寄存器用于支持<code>CPU</code>去运行这些机制。这些寄存器对外是不可见的，我们无法使用它们来进行诸如加减乘除等通用计算。这些寄存器有专门的用途，例如：全局描述符表寄存器<code>GDTR</code>用于支持保护模式运行（<code>8086</code>上没有，后续的80386就有了）、中断描述符表寄存器<code>IDTR</code>用于支持中断机制、局部描述符表寄存器<code>LDTR</code>用于支持保护模式运行、任务寄存器<code>TR</code>、控制寄存器<code>CR0~CR3</code> 、指令指针寄存器<code>IP</code>、标志寄存器<code>flags</code> 、调试寄存器<code>DR0~DR7</code></li>
<li><strong>第二类寄存器是对程序员可见的寄存器，即我们进行汇编语言程序设计时，能够直接操作的寄存器</strong>，如<code>段寄存器(CS、DS、SS)</code>、<code>通用寄存器（AX、BX、CX、DS)</code></li>
</ul>
<p><strong>虽然说第一类的程序是不可见寄存器，我们没办法直接使用，但是不少不可见寄存器是需要我们来进行初始化的</strong>。</p>
<p>此外，不管是<code>8086</code>中的哪一类寄存器，都是16位的</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116230140874.png" alt="8086中的寄存器都是16位的" style="zoom:50%;"></p>
<p>通用寄存器的介绍如下</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116230220663.png" alt="通用寄存器介绍" style="zoom: 50%;"></p>
<h3 id="3-8086的内存与地址"><a href="#3-8086的内存与地址" class="headerlink" title="3. 8086的内存与地址"></a>3. 8086的内存与地址</h3><p>上面我们介绍了<code>8086</code>中的寄存器，接下来我们介绍<code>8086</code>的内存以及地址。</p>
<p><code>CPU</code>的工作模式如下：</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116223838958.png" alt="地址总线" style="zoom:50%;"></p>
<p>我们将需要访问的内存地址交给<code>CPU</code>中的控制单元，而后控制单元从内存中对应地址的内存单元中获取数据和指令。最后将获取到的指令和操作数交给运算单元进行处理。</p>
<p><code>8086</code>中地址总线是20位宽，也就是说，<code>8086 CPU</code>能够用20个比特位来标识内存单元。<code>8086 CPU</code>中的控制单元，最多能访问$2^{20}$个内存单元。一般一个内存单元中能够储存<code>8 Bit</code>数据，也就是一个内存单元能储存一个字节的数据。因此，<code>8086 CPU</code>最多能访问$2^{20} B= 1 MB$</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116225340211.png" alt="8086中的内存与地址"></p>
<p>注意，比特位/二进制位 和 十六进制位是不同的，后面我们但凡说到<code>位</code>，可能是二进制位也可能是十六进制位，需要读者根据上下文去判断到底是那种位。</p>
<h3 id="4-8086访问内存"><a href="#4-8086访问内存" class="headerlink" title="4. 8086访问内存"></a>4. 8086访问内存</h3><h4 id="A-内存分段式访问"><a href="#A-内存分段式访问" class="headerlink" title="A. 内存分段式访问"></a>A. 内存分段式访问</h4><p>我们前面说过，<code>8086 CPU</code>访问内存是需要给出内存的地址的。而8086支持读取<code>1M</code>大小的内存，因此给出的地址必须是<code>20</code>个比特位的。可是关键问题就是<code>8086 CPU</code>，内部寄存器都是 16 位的，</p>
<p>如果我们使用单个寄存器来保存将要访问的内存地址的话，那么我们最多只能访问到<code>0x0000~0xFFFF</code>，即$2^{16}=64KB$内存。</p>
<p>因此，为了解决寄存器宽度和内存地址宽度不匹配问题，<code>8086</code>采用了<code>段基地址:段内偏移</code>的方式来表示将要访问的地址。即将20个比特位位的内存地址，拆成高16位和低16位，分别用两个寄存器保存。<strong>而在计算将要访问的内存单元的地址时，将高16位左移四位，而后再和低16位相加，就得到了最终将要访问的内存单元的地址</strong></p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116232835739.png" alt="8086计算得到20位内存单元地址"></p>
<p>上面是用二进制来解释的，用16进制来表示的话则如下：</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116233814818.png" alt="8086计算内存单元方式十六进制表示"></p>
<p><strong><code>8086</code>这种访问内存的方式，称为内存分段式访问。</strong></p>
<h4 id="B-内存分段表示法（段基地址-段内偏移）"><a href="#B-内存分段表示法（段基地址-段内偏移）" class="headerlink" title="B. 内存分段表示法（段基地址:段内偏移）"></a>B. 内存分段表示法（段基地址:段内偏移）</h4><p>一般来说，<strong>高16位地址称为段基地址/段地址，而低16位地址则称为段内偏移地址，简称为段内偏移</strong>。</p>
<p>之所以将高16位称为段地址，是因为，假设我们固定高十六位为<code>0x0FC0</code>不动，那么段内偏移地址的取值范围就是从<code>0x0000~0xFFFF</code>，此时我们能够访问到的内存单元的地址就是从<code>0x0FC00~0x1FBFF</code>。<strong>因此，在固定段地址不变的情况下，我们能够访问到<code>64K</code>个连续的内存单元，即能够访问到一小段连续的内存，因此高十六位才称为段地址</strong>。</p>
<p>高16位称为段地址理解了，那么低16位称为段内偏移就更好理解了，低16位表示了我们将要访问的内存单元在这一小段中的偏移，因此称为段内偏移地址，简称段内偏移。</p>
<p><strong>因此，相比于我们直接给出一个五位的内存地址，例如<code>0x0FC05</code>，我们现在也可以用<code>段基地址:段内偏移</code>的分段表示法来表示内存单元的地址，例如<code>0x0FC0:0x0005</code></strong></p>
<p>而在内存分段访问的方式下，一个内存单元的地址其实有多个表示方式，例如<code>0x0F111</code>，可以是<code>0x0F11:0x0001</code>，也可以是<code>0x0F10:0x0011</code>，还可以是<code>0x0F00:0x0111</code>，甚至是<code>0x0E00:0x1111</code>，只要按照规则计算出来的地址是对的就可以</p>
<h4 id="C-段寄存器与基址寄存器"><a href="#C-段寄存器与基址寄存器" class="headerlink" title="C. 段寄存器与基址寄存器"></a>C. 段寄存器与基址寄存器</h4><p>我们上面说，<code>8086</code>中将20位的内存单元地址拆分为16位的段地址与16位的段内偏移，而段地址与段内偏移分别用两个寄存器保存。</p>
<p><strong>一般来说，段地址使用专门的段寄存器来保存。这是因为，段地址在使用的时候必须要左移四位（乘以16），因此将段地址保存在特殊的寄存器中，而后使用段寄存器来计算要要访问的内存单元的地址的时候默认乘以16即可</strong>。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116234758831.png" alt="段地址默认保存在段寄存器中" style="zoom: 67%;"></p>
<p><strong>而由于低16位的段内偏移在计算的时候不需要经过特殊的计算，直接加就行了，因此可以存储在除了段寄存器以外的任何寄存器中（不严谨的说）。但是一般大家还是把段内偏移地址存储在通用寄存器中。因为通用表示既能存储加数与被加数等参与计算的数据，也能存储地址</strong>。</p>
<p>此外，虽然所有的通用寄存器都能存储地址，但是一般还是把地址存储在<code>bx</code>、<code>si</code>、<code>di</code>、<code>sp</code>和<code>bp</code>这几个寄存器中。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116235158511.png" alt="8086中的寄存器" style="zoom:50%;"></p>
<h4 id="D-段与段之间的关系"><a href="#D-段与段之间的关系" class="headerlink" title="D. 段与段之间的关系"></a>D. 段与段之间的关系</h4><p>我们上面讲到，<code>8086</code>访问内存是按照<code>段基地址:段内偏移</code>的形式来访问内存的，而<code>段基地址</code>能够去标识一段内存。所以我们其实可以把<code>1M</code>的内存分成多个不同的段。那么就有一个问题，就是<code>8086</code>把内存分出来的多个段之间的关系到底是什么样的呢？</p>
<p>事实上，段与段之间的关系可以是任意的，即段与段之间的关系完全可以是下面的三种关系之一：</p>
<ul>
<li>相邻</li>
<li>相离</li>
<li>相交</li>
</ul>
<p>这是因为，段只是为了我们表示一个地址方便，方便我们能够用<code>8086</code>中16位的寄存器去表示出来20位的地址，因此只需要能够将20位地址表示出来即可，没有必要要求段密铺满整个内存</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117001500629.png" alt="各段之间的关系" style="zoom: 33%;"></p>
<h3 id="5-总结"><a href="#5-总结" class="headerlink" title="5. 总结"></a>5. 总结</h3><p>最后，实模式下内存的访问方式就是内存分段是访问，而我们用一张图来总结总存分段式访问：</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117001836348.png" alt="总结：实模式内存分段访问"></p>
<h2 id="三、保护模式下访问内存：内存分段与内存分页"><a href="#三、保护模式下访问内存：内存分段与内存分页" class="headerlink" title="三、保护模式下访问内存：内存分段与内存分页"></a>三、保护模式下访问内存：内存分段与内存分页</h2><p>我们上面讲了<code>8086</code>的实模式，以及实模式下CPU访问内存的方式。我们接下来就要讲保护模式了。类似于实模式是伴随<code>Intel 8086 CPU</code>一起诞生的，保护模式是伴随<code>Intel 80386 CPU</code>一起诞生的。因此，我们下面也是一百年讲解<code>Intel 80386 CPU</code>，一边讲解伴随<code>Intel 80386 CPU</code>所提出的保护模式。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117011130197.png" alt="Intel 80386 CPU" style="zoom:50%;"></p>
<h3 id="1-实模式的问题"><a href="#1-实模式的问题" class="headerlink" title="1. 实模式的问题"></a>1. 实模式的问题</h3><p>在讲解保护模式之前，我们先讲讲实模式的优点和缺点，也只有这样我们才能更好的明白为什么会有保护模式。</p>
<h4 id="实模式的优点"><a href="#实模式的优点" class="headerlink" title="实模式的优点"></a>实模式的优点</h4><p><strong>实模式的优点主要就是简洁、好理解</strong>。可能一个小时左右就能够学明白实模式。</p>
<h4 id="实模式的缺点"><a href="#实模式的缺点" class="headerlink" title="实模式的缺点"></a>实模式的缺点</h4><p>实模式的缺点有很多，主要分为两个方面：</p>
<ul>
<li><strong>实模式的第一个缺点就是使用的地址都是真实的物理地址，难以支持多程序并行</strong>。如果有多个程序运行的话，多个程序都是驻留在内存中的。而由于实模式下程序使用的都是真实的物理地址，因此很有可能两个程序都要修改同一个内存单元的值。那么这样就会导致多个程序运行的结果都不会。所以实模式难以支持多个程序并行。关于不支持程序并行运行的内容，后面在分页机制中有更多的介绍。</li>
<li><strong>实模式的第二个缺点就是不安全</strong>。实模式下，程序访问、修改一个内存单元只需要给出内存单元的地址即可。而在<code>8086 CPU</code>访问地址的过程中，没有任何权限的检查，给出地址就可以访问、修改该地址对应的内存单元的内容。而由于程序可随意修改自己的段基址，因此实模式下，任何程序都可以任意访问、改变所有内存。而操作系统和用户程序一样，都是软件，在运行时都是驻留在内存中的。因此用户程序在运行的时候实际上是可以改变操作系统的程序的，因此如果用户程序写的烂的话，那么就很有可能修改了操作系统的代码，从而导致系统死机。这个时候只能断电重启，而且所有计算到一半的数据全部丢失。</li>
</ul>
<p>事实上，实模式还有第三个缺点：</p>
<ul>
<li><strong>实模式的第三个缺点就能够访问的内存太小了</strong>。<code>8086 CPU</code>的地址总线只有20位，能够访问的内存只有1M。1M的内存在1978年<code>Intel 8086 CPU</code>刚发售的时候还足够用，但是后来随着计算机快速的发展，1M的内存基本上很快就不够用了。所以实模式下，能够访问的内存太小了。这个缺点事实上是由于8086的地址总线只有20位，因此如果想要弥补这个问题的话，那么就必须要加大其地址总线的位数。</li>
</ul>
<p>实模式的缺点比较多，没办法，因为实模式是最早的<code>CPU</code>的运行模式。而也正是因为实模式有这么多的缺点，我们需要去弥补，才推动了计算机的发展，从而就有了保护模式。</p>
<p>而正是为了解决<code>8086 CPU</code>实模式的不安全性与不支持多程序并行，<code>80386 CPU</code>分别提出了保护模式和分页机制。同时通过提升CPU宽度从16位到32位，解决了访问内存有限的问题。</p>
<p>类似于实模式是伴随<code>8086 CPU</code>提出的，想要学明白保护模式和分页机制，必须要先学明白<code>80386 CPU</code>相关的知识。下面我们就将讲解<code>80386 CPU</code>，而后再讲解<code>80386 CPU</code>伴随的保护模式与分页机制。</p>
<h3 id="2-80386的运行模式"><a href="#2-80386的运行模式" class="headerlink" title="2. 80386的运行模式"></a>2. 80386的运行模式</h3><p><code>8086 CPU</code>作为最早的<code>CPU</code>，因此只能在实模式下运行。<strong>而作为后来者的<code>80386</code>一方面支持在保护模式下运行，另外一方面也能够在实模式下运行</strong>。</p>
<p>事实上，所有的后来的<code>CPU</code>都支持在实模式下运行，并且开机后就处于实模式下运行，必须要手动切换到保护模式才能运行在保护模式下。但是由于实模式存在的问题太多，因此绝大部分的操作系统，都只是在<code>CPU</code>刚上电处于实模式的时候运行操作系统加载的程序。</p>
<p>在完成操作系统最基础功能的加载后，就将<code>CPU</code>切换到保护模式下运行。</p>
<h3 id="3-80386的寄存器"><a href="#3-80386的寄存器" class="headerlink" title="3. 80386的寄存器"></a>3. 80386的寄存器</h3><p><code>8086 CPU</code>的寄存器是16位<code>CPU</code>，其寄存器是16位。而<code>80386 CPU</code>是32位的<code>CPU</code>，因此<code>80386 CPU</code>中的寄存器是32位的。</p>
<p><code>80386 CPU</code>作为从<code>8086 CPU</code>中发展出来的，其寄存器基本就是将<code>8086 CPU</code>中的16位寄存器扩展到了32位寄存器</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117024013720.png" alt="80386的寄存器" style="zoom:50%;"></p>
<p>但是为了和<code>8086 CPU</code>兼容，因此<code>80386 CPU</code>是支持仅使用低16位寄存器的。所以在<code>80386 CPU</code>刚上电的时候，<code>80386</code>和<code>8086</code>基本没啥区别，就是运行的速度比<code>8086</code>快而已。</p>
<p>此外，关于<code>80386</code>中的<code>段描述符缓冲寄存器</code>（<code>Descriptor Cache Register</code>），我们这里先不讲，等后面讲完了<code>80386</code>访问内存的方式我们再讲这个。</p>
<h3 id="4-80386的内存和地址"><a href="#4-80386的内存和地址" class="headerlink" title="4. 80386的内存和地址"></a>4. 80386的内存和地址</h3><p><code>80386 CPU</code>的工作模式和<code>8086</code>的工作模式没有变化，都是一样的：<strong>我们将需要访问的内存地址交给<code>CPU</code>中的控制单元，而后控制单元从内存中对应地址的内存单元中获取数据和指令。最后将获取到的指令和操作数交给运算单元进行处理</strong>。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116223838958.png" alt="地址总线" style="zoom:50%;"></p>
<p>我们上面说过，<code>80386</code>乃至后来的所有的<code>CPU</code>，在上电之后<code>CPU</code>默认都是在实模式下运行的，此时所有寄存器，包括地址总线都和<code>8086</code>的是一样的，因此此时在实模式下的<code>80386</code>只能访问到内存的低端<code>1MB</code>内存。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221116225340211.png" alt="80386实模式下的内存与地址"></p>
<p>在我们上电、通过手动将<code>80386</code>从实模式切换到保护模式后，才能够使用<code>80386</code>中32位的寄存器，而地址总线也从20位升级成了32位。因此，保护模式下<code>80386</code>最多能访问$2^{32}$个内存单元。一般一个内存单元中能够储存<code>8 Bit</code>数据，也就是一个内存单元能储存一个字节的数据。因此，<code>80386 CPU</code>最多能访问$2^{32} B= 4 GB$的内存。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117031124687.png" alt="80386保护模式下内存和地址"></p>
<p>注意，比特位/二进制位 和 十六进制位是不同的，后面我们但凡说到<code>位</code>，可能是二进制位也可能是十六进制位，需要读者根据上下文去判断到底是那种位。</p>
<h3 id="5-80386访问内存：保护模式（未开启分页）"><a href="#5-80386访问内存：保护模式（未开启分页）" class="headerlink" title="5. 80386访问内存：保护模式（未开启分页）"></a>5. 80386访问内存：保护模式（未开启分页）</h3><blockquote>
<p>为了解决<code>8086</code>中的不安全问题，<code>80386</code>提出了保护模式来解决不安全问题。</p>
</blockquote>
<h4 id="A-如何管理权限"><a href="#A-如何管理权限" class="headerlink" title="A. 如何管理权限"></a>A. 如何管理权限</h4><p>我们先从根源思考一下导致<code>8086 CPU</code>的实模式访问内存不安全的根源所在，而后再提出解决方案。</p>
<p>导致<code>8086 CPU</code>内存不安全访问的根本原因，就是因为我们将所有的段都视为同等地位的段，而没有区分段与段之间的区别。理论上来说，操作系统程序的段应该权限更高，用户程序在运行的时候不能访问操作系统程序的段，而能够访问用户程序自己的段。</p>
<p>所以，我们需要做的，就是为段赋予每个段的权限等级，而后修改一下计算物理地址的方式，从而在<code>8086 CPU</code>计算物理地址的基础上再额外进行权限检查。</p>
<p>因此，相比于单纯的<code>段基地址:段内偏移</code>的形式，<code>80386 CPU</code>就是使用了更加安全、高级的段描述符。</p>
<h4 id="B-段描述符（Segment-Descriptor）"><a href="#B-段描述符（Segment-Descriptor）" class="headerlink" title="B. 段描述符（Segment Descriptor）"></a>B. 段描述符（Segment Descriptor）</h4><p>对于<code>IA32</code>架构的处理器（就是我们大多数人现在所用的处理器），访问内存采用<code>段基地址:段内偏移</code>形式，即使到了保护模式，为了兼容实模式，也是绕不开这个限制的，这是骨子里的问题。所以，保护模式中访问内存依旧是按照<code>段基地址：段内偏移</code>的方式来访问内存的。</p>
<p>其次，为什么淘汰了实模式而发明了保护模式？最主要的是安全问题。基于以上两方面，<code>CPU</code>工程师既要保证保护模式下的内存访问依然是<code>段基址:段内偏移</code>的形式，又要有效提高了安全性。</p>
<p>之前在16位模式下，访问内存时只要将段基址加载到段寄存器中，再结合偏移地址就行了，段寄存器太小了，只能存储 16 位的信息，甚至连 20 位地址都要借助左移4位来实现。现在为了安全性，总该为内存段添加一些额外的安全属性吧？<strong>问题来啦，这些用于安全方面的属性，该往哪放呢？寄存器由于只有32位寄存器，也才刚刚够存放32位地址，因此额外用于提高安全性的描述段的属性的值就没有办法放在寄存器中了。排除了寄存器，自然只剩下内存了。</strong></p>
<p>相对寄存器来说，内存可是非常大的，既然有了那么大的内存可用，我们其实就可以添加更多的信息，把安全做得更加彻底一些。<strong>那么现在问题就成了：要用哪些属性来描述这个内存段呢</strong>？</p>
<p>首先，先要解决实模式下存在的问题：</p>
<ul>
<li><strong>实模式下的用户程序可以破坏存储代码的内存区域，所以要添加个内存段类型属性来阻止这种行为。</strong></li>
<li><strong>实模式下的用户程序和操作系统是同一级别的，所以要添加个特权级属性来区分用户程序和操作系统的地位。</strong></li>
</ul>
<p>其次，是一些访问内存段的必要属性条件：</p>
<ul>
<li><strong>内存段是 片内存区域，访问内存就要提供段基址，所以要有段基址属性。</strong></li>
<li><strong>为了限制程序访问内存的范围，还要对段大小进行约束，所以要有段界限属性。</strong></li>
</ul>
<p>最后，要改进就改得彻底一些，所以多增加了一些约束条件，这些马上就会讲到。</p>
<p><strong>我们这里只是说了一小部分内存段的属性，反正零零散散，一个段的基地址加上这个段的各种属性，加起来会占不少字节呢。而这些用来描述内存段的属性被放到了一个称为<code>段描述符</code>（<code>Descriptor</code>）的结构中，顾名思义，该结构专门用来描述一个内存段，该结构是8字节大小。</strong>描述符具体得结构如下：</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117033432377.png" alt="段描述符结构" style="zoom:50%;"></p>
<p>注意，段描述符是8字节大小，上面的途中为了方便展示，才将其“人为地”分成了32位和高32位，即两个4字节。而在内存中，它们是连续的8字节，这样<code>CPU</code></p>
<p>才能读取到正确的段信息。</p>
<p>关于短描述符结构的解释如下：</p>
<ul>
<li><p><strong><code>段基地址</code>：保护模式下地址总线宽度是 32 位，因此每个地址都是32位的。段基址也需要用32位地址来表示</strong>。但是段基地址之所以被拆成了三部分（<code>0~23</code>位、<code>16-19</code>、<code>24~31</code>位），这个主要是历史遗留问题。保护模式也不是一下就建立起来，也是经过了<code>8088</code>、<code>80186</code>、<code>80188</code>等好几代产品的发展，所以才导致了段基地址被拆成了三部分。</p>
</li>
<li><p><strong><code>段界限</code>表示段边界的扩展最值，即当前段向高字节最大能访问到那里，或向低字节最小访问到那里</strong></p>
<ul>
<li><p><em>扩展方向只有上下两种</em>。对于数据段和代码段，段的扩展方向是向上，即地址越来越高，此时的段界限用来表示段内偏移的最大值。对于栈段，段的扩展方向是向下，即地址越来越低， 此时的段界限用来表示段内偏移的最小值。无论是向上扩展，还是向下扩展，段界限的作用如同其名，表示段的边界、大小、范围。段界限用 20 个二进制位来表示。</p>
</li>
<li><p><em>段界限有两种不同的单位，它的单位要么是字节，要么是4KB</em>。当前段的短界限的单位到底是字节还是4KB取决于描述符中的G位来指定的。因此当前段的大小计算就是：$段界限值\times单位$，故段的大小要么是$2^{20}\ Byte = 1\ MB$，要么是$2^{20} \times 2^{12} Byte= 2^{32}Byte=4\ GB$ </p>
</li>
<li><em>段接线被拆成两部分也是由于历史问题</em>。</li>
</ul>
</li>
<li><p><strong><code>G</code>位表示段界限的粒度</strong>。<code>G=1</code>则段界限单位为<code>4KB</code>，此时当前段<code>1M</code>。如果<code>G=0</code>位为 ，表示段界限粒度大小为<code>1</code>字节。</p>
</li>
<li><p><strong><code>S</code>位表示当前段时系统段还是用户段</strong>。<code>S=0</code>则表示当前段位系统段，<code>S=1</code>则表示当前段位用户段。</p>
</li>
<li><p><code>type</code>字段目前用不到，后面再讲。</p>
</li>
<li><p><code>DPL</code>字段（<code>Descriptor Privilege Level</code>） ，即<code>描述符特权级</code>，这是保护模式提供的安全解决方案，将计算机世界按权力划分成不同等级，每一种等级称为一种特权级。这两位能表示4种特权级，分别是<code>0~3</code>级特权，数字越小，特权级越大。特权级是保护模式下才有的东西，<code>CPU</code>由实模式进入保护模式后，特权级自动为<code>0</code>。因为保护模式的代码已经是操作系统的一部分，所以操作系统应该处于最高的特权级。用户程序通常处于<code>3</code>特权级，权限最小。某些指令只能在<code>0</code>特权级下执行，从而保证了安全。</p>
</li>
<li><p><code>P</code>字段（<code>Present</code>） ，即段是否存在。如果段存在于内存中，则<code>P=1</code> ，否则<code>P=0</code>。<code>P</code>宇段是由<code>CPU</code>来检查的，如果为<code>0</code>, <code>CPU</code>将抛出异常，转到相应的异常处理程序，此异常处理程序是咱们来写的，在异常处理程序处理完成后（即将段从外存中调入到内存的之后）要将<code>P</code>设置为<code>1</code>。也就是说，对于<code>P</code>宇段， CPU 只负责检查，咱们负责赋值。不过在通常情况下，段都是在内存中的</p>
</li>
<li><p><code>AVL</code>字段（<code>AVaiLable</code>） ，用来表示当前段是否是可用的。不过这可用是对用户来说的，操作系统可以随意用此位。对硬件来说，它没有专门的用途，这个是专门给软件使用的。</p>
</li>
<li><p><code>L</code>字段（<code>Length</code>），用来表示当前段是否是64位代码段。<code>L=1</code>表示当前段为<code>64</code>位代码段，否则表示<code>32</code>位代码段。这目前属于保留位，因为我们目前讨论的是32位的<code>CPU</code>，所以这一位为<code>0</code>便可。</p>
</li>
<li><p><code>D/B</code>字段，用来指示有效地址（段内偏移地址）及操作数的大小。</p>
<ul>
<li>有没有觉得奇怪，实模式己经是32位的地址线和操作数了，难道操作数不是32位大小吗？其实这是为了兼容<code>80286 CPU</code>的保护模式，<code>80286 CPU</code>的保护模式下的操作数是16位。既然是指定“操作数”的大小，也就是对“指令”来说的，与指令相关的内存段是代码段和枝段，所以此字段是<code>B</code>。对于代码段来说，<code>D=0</code>表示指令中的有效地址和操作数是16位，段内偏移用<code>IP</code>寄存器。若 <code>D=1</code>表示指令中的有效地址及操作数是 32 位，段内偏移用 <code>EIP</code> 寄存器。</li>
<li>对于栈段来说，此位是<code>B</code>位，用来指定操作数大小，此操作数涉及到栈指针寄存器的选择及栈的地址上限。若<code>B=0</code>，则使用的是<code>sp</code>寄存器，也就是栈的起始地址是 16 位寄存器的最大寻址范围， <code>0xFFFF</code>。若<code>B=1</code>，则使用的是<code>esp</code>寄存器，也就是栈的起始地址是32位寄存器的最大寻址范围， <code>0xFFFFFFFF</code></li>
</ul>
</li>
</ul>
<p>补一个短描述符指向内存的图</p>
<h4 id="C-全局段描述符表与GDTR寄存器"><a href="#C-全局段描述符表与GDTR寄存器" class="headerlink" title="C. 全局段描述符表与GDTR寄存器"></a>C. 全局段描述符表与GDTR寄存器</h4><p>一个段描述符只用来定义（描述）一个内存段。代码段要占用一个段描述符、数据段和战段等，多个内存段也要各自占用一个段描述符，这些描述符放在哪里呢？答案是放在<code>全局描述符表</code>（<code>Global Descriptor Table, GDT</code>）中。<code>全局描述符表</code>相当于是描述符的数组，数组中的每个元素都是8字节的描述符。可以用选择子（马上会讲到）中提供的下标在<code>GDT</code>中索引描述符。</p>
<p>为什么将该表称为<strong>全局</strong>段描述符表？全局体现在多个程序都可以在<code>全局段描述符表</code>中添加自己的段描述符，因此<code>全局段描述符表</code>是系统公用的，因此称为<code>全局段描述符表</code>。</p>
<p><code>全局描述符表</code>位于内存中，需要用专门的寄存柑旨向它后，<code>CPU</code>才知道它在哪里。这个专门的寄存器便是<code>GDTR</code>，即<code>GDT Regiter</code>，专门用来存储<code>GDT</code>的内存地址及大小。 <code>GDTR</code>是个48位的寄存器，其结构如下：</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117142709767.png" alt="GDTR寄存器结构" style="zoom:50%;"></p>
<p>事实上，我们在最前面介绍过<code>CPU</code>中的寄存器分为两类，一类是程序员可见的寄存器，用于完成通用的计算，另外一类是程序员不可见的寄存器，用于支持CPU运行的机制，程序员只负责初始化。<code>GDTR</code>寄存器就是程序员不可见的寄存器中的一种。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117145947782.png" alt="短描述符与内存段的关系" style="zoom:50%;"></p>
<h4 id="D-lgdt指令"><a href="#D-lgdt指令" class="headerlink" title="D. lgdt指令"></a>D. lgdt指令</h4><p>而对于<code>GDTR</code>寄存器的初始化，不能够用<code>mov gdtr</code>这样的汇编指令进行初始化，有专门的指令来做这件事，这就是<code>lgdt</code>指令。<code>lgdt</code>是专门用于设置<code>GDTR</code>寄存器的指令。而<code>lgdt</code>这个指令非常特殊，我们在前面说任何<code>CPU</code>刚上电都是处于实模式，需要用户手动切换到保护模式中去，因此<code>lgdt</code>这个指令就是在实模式下运行的指令，而设置<code>GDTR</code>寄存器就是从实模式到保护模式需要干的事之一。</p>
<p>但事实上，进入保护模式需要有<code>GDT</code>，但进入保护模式后，可能还要重新加载一个新的<code>GDT</code>。在保护模式下重新换个<code>GDT</code>的原因是实模式下只能访问低端<code>1MB</code>空间，所以<code>GDT</code>只能位于低端<code>1MB</code>之内。但在进入保护模式后，访问的内存空间突破了<code>1MB</code>的限制，根据操作系统的实际情况，有可能需要把<code>GDT</code>放在内存的其他位置上，因此在进入保护模式之后，可能还需要重新加载<code>GDTR</code>寄存器，<strong>因此<code>lgdt</code>指令实际上既能在实模式下运行，也能在保护模式下运行</strong>。</p>
<p><code>ldgt</code>指令的格式如下</p>
<pre class="line-numbers language-assembly"><code class="language-assembly">lgdt 48位内存数据
<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
<p>之所以是48位内存数据，主要就是<code>GDTR</code>寄存器是48位的，6个字节。因此<code>lgdt</code>指令后面其实只需要给第一个字节的内存单元地址即可，<code>CPU</code>会自动的读取6个字节。</p>
<h4 id="E-段选择子-段内偏移"><a href="#E-段选择子-段内偏移" class="headerlink" title="E. 段选择子:段内偏移"></a>E. 段选择子:段内偏移</h4><p>段描述符有了，描述符表也有了，我们该如何使用它呢？下面我们引出新的概念<code>段选择子</code>。</p>
<p><strong>段寄存器<code>CS</code>/<code>DS</code>/<code>ES</code>/<code>FS</code>/<code>GS</code>/<code>SS</code>，在实模式下时，存储的是段基地址，即内存段的起始地址。而在保护模式下时，由于段基址已经存入了段描述符中，所以段寄存器中再存放段基址是没有意义的，此时在段寄存器中存入的是一个称为选择子（<code>Selector</code>）的东西。</strong></p>
<p><strong>选择子本质上就是一个索引值（其实还有其他属性）。用此索引值在段描述符表中索引就可以得到相应的段描述符，这样，便在段描述符中得到了内存段的起始地址和段界限值等相关信息。因此，在保护模式下，我们访问内存依旧是通过内存分段的方式访问的内存，但是不同的是，此时是<code>段选择子:段内偏移</code>的形式去访问内存的</strong></p>
<p>实模式下段选择子的结构如下：</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117150045119.png" alt="段选择子结构" style="zoom:50%;"></p>
<p>由于实模式下段寄存器是16位，所以段选择子也是16位。</p>
<ul>
<li>段选择子低2位即第<code>0~1</code>位，用来存储<code>RPL</code>（<code>Request Privilege Level</code>），即<code>请求特权级</code>，可以表示<code>0~3</code>四种特权级。关于<code>RPL</code>可以简单的理解为请求者的当前特权级（不理解也没关系，因为我们这里着重讲解访问内存的方式，在这里他不重要）</li>
<li>段选择子的第2位是<code>TI</code>位，即<code>Table Indicator</code>，用来指示选择子是在<code>GDT</code>中，还是<code>LDT</code>中索引描述符（<code>LDT</code>我们稍后就会讲到）。<code>TI=0</code>表示在<code>GDT</code>中索引描述符，<code>TI=1</code>表示在<code>LDT</code>中索引描述符</li>
<li>选择子的高13位，即<code>3~15</code>位是描述符的索引值，用来在<code>GDT</code>中索引描述符（<code>TI=0</code>时）。前面说过<code>GDT</code>相当于一个描述符数组，所以选择子中的索引值就是<code>GDT</code>中的下标。</li>
</ul>
<p>需要注意的是，由于实模式下选择子的索引值有13位，而$2^{13}=8192$ ，故实模式下，段选择子最多可以索引8192个段，因此实模式下<code>GDT</code>中最多定义8192个描述符。</p>
<p><strong>选择子的作用主要是确定段描述符，确定描述符的目的，一是为了特权级、界限等安全考虑，最主要的还是要确定段的基地址。虽然到了保护模式，但<code>IA32</code>架构始终脱离不了内存分段，即访问内存必须要用<code>段基址:段内偏移地址</code>的形式。保护模式下的段寄存器中已经是选择子，不再是直接的段基址。段基址在段描述符中，用给出的选择子索引到描述符后，<code>CPU</code>自动从段描述符中取出段基址，这样再加上段内偏移地址，便凑成了<code>段基址:段内偏移地址</code>的形式</strong></p>
<p>所以在保护模式下，我们就从原本最简单的<code>段基地址:段内偏移</code>的形式转变成了<code>段选择子:段内偏移</code>的形式。而且由于<code>段描述符</code>中的段基地址就是32位的，因此不需要再将段基址乘以16后再与段内偏移地址相加。</p>
<blockquote>
<p>举个例子：</p>
<p>假设选择子是<code>0x8</code>，将其加载到<code>ds</code>寄存器后，访问<code>ds:0x9</code> 所对应的内存单元。其过程为：</p>
<ol>
<li><strong>拆分选择子</strong>。<code>0x8</code> 的<code>0~1</code>位是<code>RPL</code>位，其值为<code>00</code>。第<code>2</code>位是<code>TI</code>位，其值表示是在<code>GDT</code>中索引段描述符还是在<code>LDT</code>中索引段描述符。这里<code>TI=0</code>所以就是在<code>GDT</code>中索引段描述符。用<code>0x8</code>的高13位<code>0x1</code>在<code>GDT</code>中索引。</li>
<li><strong>索引段描述符</strong>。这里高13位是<code>0x1</code>，因此索引的就是也就是<code>GDT</code>中的第1个段描述符（ <strong><code>GDT</code>中第0个段描述符故意置空，是不能用的，因此可用的段描述符从索引1开始</strong>）。</li>
<li><strong>拼接段基地址和段内偏移</strong>。假设第<code>1</code>个段描述符中的3个段基址部分，拼接后的值为<code>0xl234</code>，则<code>CPU</code>将<code>0xl234</code>作为段基址，与段内偏移地址<code>0x9</code>相加，<code>0x1234+0x9=0x123D</code></li>
<li><strong>访问内存地址</strong>。用所得的和<code>0xl23D</code>作为访存地址，开始访问</li>
</ol>
<p>注意，我们上面故意忽略了诸如<code>PRL</code>特权检查等等步骤，是为了强调最终物理地址获取的流程</p>
</blockquote>
<p>上面例子中提到了<code>GDT</code>中的第0个段描述符是不可用的，原因是定义在<code>GDT</code>中的段描述符是要用选择子来访问的，如果使用的选择子忘记初始化，选择子的值便会是<code>0</code> ，这便会访问到第<code>0</code>个段描述符。为了避免出现这种因忘记初始化选择子而选择到第<code>0</code>个段描述符的情况， <code>GDT</code>中的第<code>0</code>个段描述符不可用。也就是说，若选择到了<code>GDT</code>中的第<code>0</code>个段描述符，处理器将发出异常。</p>
<h4 id="F-局部段描述符表"><a href="#F-局部段描述符表" class="headerlink" title="F. 局部段描述符表"></a>F. 局部段描述符表</h4><blockquote>
<p>按理说全局段描述符强调全局就是为了和局部区别开，那么我们就会问到底有没有局部段描述符表？还真有。</p>
</blockquote>
<p><strong><code>局部描述符表</code>（<code>Local Descriptor Table,LDT</code>） ，它是<code>CPU</code>厂商为在硬件一级为原生支持多任务而创造的表。按照硬件厂商的设想，一个任务对应一个 LDT 。然而，在现代操作系统中很少有用 LDT 的，属实是硬件厂商的意淫了</strong>。所以这里就捎带着说一下，点到为止。</p>
<p><code>CPU</code>厂商建议每个任务的私有内存段都应该放到自己的段描述符表中，该表就是 LDT，即每个任务都有自己的 LDT 。随着任务切换，也要切换相应任务的<code>LDT</code>。<code>LDT</code>也位于内存中，其地址需要先被加载到某个寄存器后，<code>CPU</code> 才能使用<code>LDT</code>，该寄存器是<code>LDTR</code>，即<code>LDT Register</code>。同样也有专门的指令用于加载<code>LDT</code>，即<code>lldt</code>。以后每切换任务时，都要用<code>lldt</code>指令重新加载任务的私有内存段。</p>
<p><code>LDT</code>虽然是个表，但其也是一片内存区域，所以也需要用全局段描述符（即<code>GDT</code>中的描述符）在<code>GDT</code>中先注册。段描述符是需要用选择子去访问的。故<code>lldt</code>的指令格式为：</p>
<pre class="line-numbers language-assembly"><code class="language-assembly">lldt 16位寄存器/16为内存
<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
<p>无论是寄存器，还是内存，其内容一定是个选择子，该选择子用来在<code>GDT</code>中索引<code>LDT</code>的段描述符。</p>
<p><code>LDT</code>被加载到<code>LDTR</code>寄存器后，之后再访问某个段时，选择子中的<code>TI</code>位若为<code>1</code> ，就会用该选择子中的高13位在<code>LDTR</code>寄存器所指向的<code>LDT</code>中去索引相应段描述符。</p>
<p><code>LDT</code>中的段描述符和<code>GDT</code>中的一样，与<code>GDT</code>不同的是<code>LDT</code>中的第0个段描述符是可用的，因为访问内存时提交的选择子中的<code>TI</code>位，<code>TI</code>位用于指定<code>GDT</code>，还是 <code>LDT</code>，<code>TI=1</code> 则表示在<code>LDT</code>中索引段描述符，即必然是经过显式初始化的结果，完全排除了忘记初始化的可能。</p>
<h4 id="G-总结"><a href="#G-总结" class="headerlink" title="G. 总结"></a>G. 总结</h4><p>在保护模式下，<strong>未开始分页机制时</strong>，通过<code>段选择子:段内偏移</code>这一访问内存的方式流程我们上面就全部讲解完了。</p>
<p>总结如下：</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117153736973.png" alt="段选择子:段内偏移访问内存"></p>
<h3 id="6-80386访问内存：保护模式（开启分页）"><a href="#6-80386访问内存：保护模式（开启分页）" class="headerlink" title="6. 80386访问内存：保护模式（开启分页）"></a>6. 80386访问内存：保护模式（开启分页）</h3><blockquote>
<p>为了解决<code>8086</code>不支持多程序并行运行问题，<code>80386</code>在保护模式的基础上提出了分页机制来解决程序并行运行问题。</p>
</blockquote>
<p>我们在上面说到，<code>8086 CPU</code>存在的最大的两个问题，一个是安全性问题，另外一个就是不支持多程序并行运行。<code>80386</code>中使用保护模式主要是解决了安全性问题，但是即便是保护模式，也依旧没有解决并行运行问题。</p>
<p>为了解决并行运行问题，<code>80386 CPU</code>中还提出了分页机制。</p>
<p><strong>再开始讲解分页机制之前，我们需要明白的是，分页机制和保护模式两者并不是互相冲突的，也就是说并不是开启了保护模式就不能使用内存分页机制，使用内存分页机制就不能开启保护模式。相反，内存分页机制是建立在保护模式基础上的，内存分页机制就是为了提高保护模式不支持多程序并行运行的问题才提出来的。</strong></p>
<h4 id="A-内存分段机制的问题：内存碎片"><a href="#A-内存分段机制的问题：内存碎片" class="headerlink" title="A. 内存分段机制的问题：内存碎片"></a>A. 内存分段机制的问题：内存碎片</h4><p><strong>前面不管是实模式还是保护模式，我们访问内存都是通过内存分段的方式进行的</strong>。即将内存中的某一小段连续的内存分配给某个进程使用，而后该进程就使用这段内存。</p>
<p>可是，我们在最前面说内存分段机制不支持多个程序并行运行，我们通过举下面的例子来进行说明。</p>
<blockquote>
<p><strong>我们模拟多个进程并行的情况</strong>。</p>
<ol>
<li>在第1步中，系统里有3个进程正在运行，进程<code>A</code>、<code>B</code>、<code>C</code>各占<code>10MB</code>、<code>20MB</code>、<code>30MB</code>内存，物理内存还剩下<code>15MB</code>可用</li>
<li>到了第2步，此时<code>进程B</code>己经运行结束，腾出<code>20MB</code>的内存，可是待加载运行的<code>进程D</code>需要<code>20MB+3KB</code>的内存空间，即<code>20483KB</code>。现在的运行环境未开启分页功能，“段基址＋段内偏移”产生的线性地址就是物理地址，程序中引用的线性地址是连续的，所以物理地址也连续。虽然总共剩下<code>35MB</code>内存可用，可问题是明摆着的，现在连续内存块只有原来<code>进程B</code>运行结束后释放的<code>20MB</code>和最下面可用内存<code>15MB</code>，哪一块都不够<code>进程D</code>用，这时候<code>进程D</code>就无法运行</li>
</ol>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117200710973.png" alt="多个进行并行" style="zoom:50%;"></p>
</blockquote>
<p><strong>明明空闲的内存是足以容纳新的进程来运行的，但是现实情况就是我们没有足够的<code>连续</code>的内存来运行新的进程。</strong>实际上，<strong>这就于内存分段机制最大的问题：内存分段机制会造成内存碎片，从而降低了内存的使用率以及降低了并发性。</strong></p>
<p>为了提高并发性（即让上面的<code>进程D</code>运行），我们有很多的方法，第一个方法就是把暂时不用的进程从内存挪出到外存中，这样就有足够的内存空间容纳新的进程（如果换出的进程不好的话还是会没有足够的内存空间），但是这个方法不是我们这里要关注的重点，我们接下来讲从另外一个角度来解决这个问题。</p>
<h4 id="B-内存分页机制"><a href="#B-内存分页机制" class="headerlink" title="B. 内存分页机制"></a>B. 内存分页机制</h4><p>问题的本质是在目前只分段的情况下， <code>CPU</code>认为<code>线性地址</code>等于<code>物理地址</code>。而<code>线性地址</code>是由编译器编译出来的，它本身是连续的，所以<code>物理地址</code>也必须要连续才行，但在内存分段的机制下，我们<strong>可用</strong>的<code>物理地址</code>不连续。换句话说，如果<code>线性地址</code>连续，而<code>物理地址</code>可以不连续，不就解决了吗?</p>
<p><strong>按照这种思路，我们首先要做的是解除线性地址与物理地址一一对应的关系，然后将它们的关系重新建立。通过某种映射关系，可以将线性地址映射到任意物理地址。</strong></p>
<p><strong>所以，我们只需要在段部件计算的基础上，在加一层映射，将段部件输出的连续的线性地址映射为不连续的物理地址。此时段部件的线性地址页称为<code>虚拟地址</code></strong></p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117205416970.png" alt="内存分页机制"></p>
<p>因此，在带上分页机制一起说的话，此时<code>80386</code>访问内存的机制为：</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117210135785.png" alt="80386访问内存的机制" style="zoom:50%;"></p>
<p>为了讲明白分页机制，我们下面将从两个方面来讲解：</p>
<ul>
<li>分页机制的原理</li>
<li>页表的结构</li>
</ul>
<h4 id="C-分页机制（一级页表）的原理"><a href="#C-分页机制（一级页表）的原理" class="headerlink" title="C. 分页机制（一级页表）的原理"></a>C. 分页机制（一级页表）的原理</h4><p>想整明白分页机制的原理，就得整明白映射。分页机制中最重要的就是映射。</p>
<blockquote>
<p>映射这个概念大家应该比较清楚，对应的英文单词是<code>map</code>。本质上就是通过函数<code>f</code>将元素<code>A</code>映射为<code>B</code>。即<code>B=f(A)</code>，函数<code>f</code>可以是查表，也可以诸如哈希函数线性相乘取余。</p>
</blockquote>
<h5 id="逐字节映射"><a href="#逐字节映射" class="headerlink" title="逐字节映射"></a>逐字节映射</h5><p>在内存地址中，最简单的映射方法是<code>逐字节映射</code>，即<strong>一个线性地址对应一个物理地址</strong>。比如线性地址<code>0x0</code>，其对应的物理地址可以是<code>0x0</code>、<code>0x10</code> 或其他你喜欢的数字。若线性地址为<code>0x1</code>，对应的物理地址<code>0xl</code>、<code>0x11</code> 或其他你喜欢的数字。</p>
<p>但是不管怎么说，我们需要找个地方来存储这种映射关系， 这个地方就是<code>页表</code>（<code>Page Table</code> ）。<code>页表</code>就是一个N行1列的表格，页表中的每一行（只有一个单元格）称为<code>页表项</code>（<code>Page Table Entry</code>，<code>PTE</code>），其大小是4字节，页表项的作用是存储内存物理地址。当访问一个线性地址时，实际上就是在访问页表项中所记录的物理内存地址。</p>
<p>下图表示了<code>逐字节映射</code>方式下页表和物理内存的关系，即一个页表项指向一个物理内存。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117214219623.png" alt="逐字节映射页表和物理内存的关系" style="zoom:50%;"></p>
<p>但是，<code>逐字节映射</code>这样的方式有很大的问题：</p>
<ol>
<li>因为有4G个物理内存单元，因此页表中就应该有 4G 个页表项</li>
<li>32位的物理地址要用4个字节的页表项来存储，那么页表总共大小是$4Byte * 4G=16GB$</li>
</ol>
<p><strong>分页机制本质上是将大小不同的大内存段拆分成大小相等的小内存块。以上方案其实就是将<code>4GB</code>间划分成<code>4G</code>个内存块，每个内存块大小是1字节。但页表也是存储在内存中的，若按此<code>逐字节映射</code>的方案，光是页表就要占16GB内存，得不偿失，显然方案不合理</strong>。</p>
<h5 id="逐块映射"><a href="#逐块映射" class="headerlink" title="逐块映射"></a>逐块映射</h5><p><strong>任意进制的数字都可以分成高位部分和低位部分，若将低位部分理解为单位内存块大小，高位部分则是这种内存块的数量</strong>。例如六万的十进制可表示为<code>60000</code> ，也可以表示为<code>60千</code>，也就是将<code>60000</code>分成<code>高位60</code>和<code>低1000</code>两部分。</p>
<p>类似的，32位地址表示4GB空间，我们也可以可以将32位地址分成高低两部分，<code>低地址部分是内存块大小，高地址部分是内存块数量</code>。它们是这样一种关系：<code>内存块数＊内存块大小=4GB</code> 。</p>
<p>形象的理解为：有一个滑块在32位地址上左右滑动。滑块右边是内存块尺寸，滑块左边是内存块数量。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117214759519.png" alt="形象的理解内存地址" style="zoom:50%;"></p>
<p>而页表的大小则是：<code>4 Byte ＊ 内存块数量</code>，因此，为了降低页表的大小，我们就需要左右移动滑块找到合适的内存块尺寸。</p>
<p>如果滑块指向第20位，内存块大小为$2^{20}$次方，即<code>1MB</code>，内存块数量为$2^{12}$个，即<code>4K</code>个。若滑块指向第12位，内存块大小则为$2^{12}$次方，即<code>4KB</code>，内存块数量则为$2^{20}$个，<code>1M</code>个 ，即<code>1048576</code> 个。</p>
<p><strong>这里所说的内存块，其官方名称是页（Page）， CPU 中采用的页大小恰恰就是<code>4KB</code>，也就是上图中滑块的落点处。</strong></p>
<p><strong>页是地址空间的计量单位，并不是专属物理地址或线性地址，只要是<code>4KB</code>的地址空间都可以称为一页，所以线性地址的一页也要对应物理地址的一页。一页大小为 <code>4KB</code> ，这样一来，<code>4GB</code>地址空间被划分<code>4GB/4KB=1M</code>个页，也就是<code>4GB</code>空间中可以容纳<code>1048576</code>个页，页表中自然也要有<code>1048576</code>个页表项，这就是一级页表模型。</strong></p>
<h5 id="一级页表模型"><a href="#一级页表模型" class="headerlink" title="一级页表模型"></a>一级页表模型</h5><p>下图所示是一级页表模型，由于页大小是<code>4KB</code>，所以页表项中的物理地址都是<code>4K</code>的整数倍，故用十六进制表示的地址，低3位都是0。就拿第3个页表项来说，其值为<code>0x3000</code> ，表示该页对应的<code>物理地址</code>是<code>0x3000</code></p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117220815916.png" alt="一级页表" style="zoom:50%;"></p>
<h5 id="地址转换"><a href="#地址转换" class="headerlink" title="地址转换"></a>地址转换</h5><p>我们上面介绍了一级页表模型，其中每<code>4K</code>个内存单元组成一个页，每个内存单元是<code>1</code>个字节，所以一个页就是<code>4KB</code>，而一共有<code>1M</code>个页。</p>
<p>可是，我们有个问题，就是：页表如何使用？或者说如何通过页表将线性地址转换成物理地址？这个还是得用上面的图来帮助我们理解。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117214759519.png" alt="线性地址转换为物理地址" style="zoom:50%;"></p>
<p>滑块正落到在32位地址的第12位。<strong>右边第<code>11~0</code>位用来表示页的大小，也就是这12位可以作为页内寻址，因此称为<code>页内偏移</code></strong>。<strong>左边第<code>31~12</code>位用来表示页的数量，同样这20位也可以用来索引一个页（索引范围<code>0x00000~0xfffff</code>），表示第几个页，因此称为<code>页索引</code></strong>。</p>
<p>经以上分析，虚拟地址的高20位可用来定位一个物理页，低12位可用来在该物理页内寻址。所以现在问题就变成了：给定<code>虚拟地址</code>，如何获取这个虚拟地址对应的物理页的地址？</p>
<p>其实这也很简单：</p>
<ul>
<li>页表项中保存了物理页的地址，而页表项位于内存中，所以只要提供页表项的物理地址便能够访问到页表项。</li>
<li>页表项保存在页表中。页表本身是线性表这种数据结构，相当于页表项数组，访问其中任意页表项成员，只要知道该表页项的索引（下标）就够了</li>
</ul>
<p><strong>因此，为了获得虚拟地址对应的物理页的地址，我们只需要得知指向该物理页的页表项在页表中的索引即可</strong>。</p>
<p>而类似于<code>CPU</code>上电后要手动打开保护模式，我们在进入到保护模式后也要手动打开<code>分页机制</code>。手动打开分页机制的时候要将<code>页表地址</code>加载到控制寄存器<code>cr3</code>中。</p>
<p>所以，在打开分页机制后，页表的物理地址实际上就在寄存器<code>cr3</code>中，我们只需要按照<code>页表物理地址 + 索引 * 4</code>（一个页表项4个字节），就能够获得页表项的物理地址了。</p>
<p>获得页表项的物理地址之后，再加上页内便宜，我们就得到了将要访问的物理地址。</p>
<blockquote>
<p>举一个保护模式 + 内存分页机制下访问内存的例子：</p>
<p>假设我们采用<code>平坦内存模型</code>，即<strong>不管段选择子值是多少，其所指向的段基址都是<code>0x0000</code></strong> ，现在求指令<code>mov ax，[0xl234］</code>将访问的内存单元的物理地址。</p>
<p><strong>首先进行段式访问内存地址处理</strong>：</p>
<ol>
<li>在指令<code>mox ax, [0x1234]</code>中，<code>0x1234</code>是<code>段基址/段选择子：段内偏移地址</code>中的<code>段内偏移地址</code>。</li>
<li>由于我们采用平坦内存模型，因此选择子最终在<code>GDT</code>中获得的段基址是<code>0x0000_0000</code></li>
<li>因为段内偏移地址为<code>0x1234</code>，所以经过段部件处理后，输出的线性地址/虚拟地址是<code>0x0000_l234</code>。</li>
</ol>
<p><strong>然后进行页式访问内存地址处理</strong>（由于咱们是演示分页机制，所以系统已经打开了分页机制，线性地址<code>0x0000_1234</code>被送入了页部件进行转换以获得物理地址）：</p>
<ol>
<li><strong>页部件分析<code>0x0000_1234</code>的高20位</strong>，用十六进制表示高20位是<code>0x0000_1</code> 。所以将此项作为页表项索引，再将该索引乘以<code>4</code>后加上<code>cr3</code>寄存器中页表的物理地址，这样便得到索引所指代的页表项的物理地址，从该物理地址处（页表项中〉读取所映射的物理页地址为<code>0x9000</code>。</li>
<li><strong>叶部件分析<code>0x0000_1234</code>的底12位</strong>，线性地址的低12位是<code>0x234</code>，它作为物理页的页内偏移地址与物理页地址。</li>
<li><strong>计算最终的物理地址</strong>。物理页地址<code>0x9000</code>与页内偏移<code>0x234</code>相加，和为<code>0x9234</code>，这就是虚拟地址<code>0xl234</code>最终转换成的物理地址</li>
</ol>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117223210738.png" alt="保护模式+内存分段访问内存的例子" style="zoom:50%;"></p>
<p>​    </p>
</blockquote>
<h4 id="D-分页机制的作用"><a href="#D-分页机制的作用" class="headerlink" title="D. 分页机制的作用"></a>D. 分页机制的作用</h4><p>分页机制的作用有两方面：</p>
<ul>
<li><strong>将<code>线性地址</code>/<code>虚拟地址</code>转换成<code>物理地址</code></strong></li>
<li><strong>用大小相等的页代替大小不等的段</strong></li>
</ul>
<p>这两方面的作用如下图所示：</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117211342247.png" alt="分页机制的作用" style="zoom:50%;"></p>
<p><strong>由于有了<code>线性地址</code>到<code>真实物理地址</code>的这层映射，经过段部件输出的<code>线性地址</code>便有了另外一个名字：<code>虚拟地址</code></strong>。</p>
<p>分页机制有一个重要的功能，就是将每个程序眼中的地址隔离开。在内存分段访问的情况下，如果有两个程序通过<code>段基地址:段内偏移</code>或者<code>段选择子:段内偏移</code>的方式计算出来的地址是一样的，那么两个程序的地址就会有冲突。</p>
<p><strong>然而，在分页机制下，因为两个程序将虚拟地址映射为物理地址的方式不一样，因此即便是两个程序计算出来的地址是一样的，但两个程序映射方式不一样，因此最终计算得到的物理地址也不一样。因此，内存分页机制实现了程序之间的相互隔离，每个程序都有自己虚拟地址空间</strong>。</p>
<h4 id="E-总结"><a href="#E-总结" class="headerlink" title="E. 总结"></a>E. 总结</h4><p>在保护模式下，<strong>开始分页机制时</strong>，先通过<code>段选择子:段内偏移</code>这一段式访问内存先计算出虚拟地址，而后根据页式地址转换最终获得物理地址的流程我们上面就全部讲解完了。</p>
<p>总结如下：</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117234106913.png" alt="段式内存访问（段选择子:段内偏移）+页式内存访问总结"><img src="" alt="image-20221117234106726"></p>
<h3 id="7-80386访问内存总结"><a href="#7-80386访问内存总结" class="headerlink" title="7. 80386访问内存总结"></a>7. 80386访问内存总结</h3><p>最后，我们总结一下<code>80386</code>的保护模式以及内存分页机制访问内存。</p>
<p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117233947280.png" alt="80386访问内存总结"></p>
<h2 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h2><p><img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221117233832116.png" alt="实模式与保护模式访问内存"></p>

                
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="/about" rel="external nofollow noreferrer">Jack Wang</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="https://jackwang0107.github.io/2022/11/15/cao-zuo-xi-tong-fang-wen-nei-cun/">https://jackwang0107.github.io/2022/11/15/cao-zuo-xi-tong-fang-wen-nei-cun/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="/about" target="_blank">Jack Wang</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            
                                <a href="/tags/%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80/">
                                    <span class="chip bg-color">物理地址</span>
                                </a>
                            
                                <a href="/tags/%E9%80%BB%E8%BE%91%E5%9C%B0%E5%9D%80/">
                                    <span class="chip bg-color">逻辑地址</span>
                                </a>
                            
                                <a href="/tags/%E6%9C%89%E6%95%88%E5%9C%B0%E5%9D%80/">
                                    <span class="chip bg-color">有效地址</span>
                                </a>
                            
                                <a href="/tags/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80/">
                                    <span class="chip bg-color">线性地址</span>
                                </a>
                            
                                <a href="/tags/%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80/">
                                    <span class="chip bg-color">虚拟地址</span>
                                </a>
                            
                                <a href="/tags/%E5%AE%9E%E6%A8%A1%E5%BC%8F/">
                                    <span class="chip bg-color">实模式</span>
                                </a>
                            
                                <a href="/tags/%E4%BF%9D%E6%8A%A4%E6%A8%A1%E5%BC%8F/">
                                    <span class="chip bg-color">保护模式</span>
                                </a>
                            
                                <a href="/tags/%E6%AE%B5%E9%80%89%E6%8B%A9%E5%AD%90/">
                                    <span class="chip bg-color">段选择子</span>
                                </a>
                            
                                <a href="/tags/%E6%AE%B5%E6%8F%8F%E8%BF%B0%E7%AC%A6%E8%A1%A8/">
                                    <span class="chip bg-color">段描述符表</span>
                                </a>
                            
                                <a href="/tags/%E7%9F%AD%E6%8F%8F%E8%BF%B0%E7%AC%A6/">
                                    <span class="chip bg-color">短描述符</span>
                                </a>
                            
                                <a href="/tags/%E5%86%85%E5%AD%98%E5%88%86%E6%AE%B5/">
                                    <span class="chip bg-color">内存分段</span>
                                </a>
                            
                                <a href="/tags/%E5%86%85%E5%AD%98%E5%88%86%E9%A1%B5/">
                                    <span class="chip bg-color">内存分页</span>
                                </a>
                            
                                <a href="/tags/%E9%A1%B5%E8%A1%A8/">
                                    <span class="chip bg-color">页表</span>
                                </a>
                            
                                <a href="/tags/%E9%A1%B5%E8%A1%A8%E9%A1%B9/">
                                    <span class="chip bg-color">页表项</span>
                                </a>
                            
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/libs/share/css/share.min.css">
<div id="article-share">

    
    <div class="social-share" data-sites="twitter,facebook,google,qq,qzone,wechat,weibo,douban,linkedin" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
                <style>
    #reward {
        margin: 40px 0;
        text-align: center;
    }

    #reward .reward-link {
        font-size: 1.4rem;
        line-height: 38px;
    }

    #reward .btn-floating:hover {
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2), 0 5px 15px rgba(0, 0, 0, 0.2);
    }

    #rewardModal {
        width: 320px;
        height: 350px;
    }

    #rewardModal .reward-title {
        margin: 15px auto;
        padding-bottom: 5px;
    }

    #rewardModal .modal-content {
        padding: 10px;
    }

    #rewardModal .close {
        position: absolute;
        right: 15px;
        top: 15px;
        color: rgba(0, 0, 0, 0.5);
        font-size: 1.3rem;
        line-height: 20px;
        cursor: pointer;
    }

    #rewardModal .close:hover {
        color: #ef5350;
        transform: scale(1.3);
        -moz-transform:scale(1.3);
        -webkit-transform:scale(1.3);
        -o-transform:scale(1.3);
    }

    #rewardModal .reward-tabs {
        margin: 0 auto;
        width: 210px;
    }

    .reward-tabs .tabs {
        height: 38px;
        margin: 10px auto;
        padding-left: 0;
    }

    .reward-content ul {
        padding-left: 0 !important;
    }

    .reward-tabs .tabs .tab {
        height: 38px;
        line-height: 38px;
    }

    .reward-tabs .tab a {
        color: #fff;
        background-color: #ccc;
    }

    .reward-tabs .tab a:hover {
        background-color: #ccc;
        color: #fff;
    }

    .reward-tabs .wechat-tab .active {
        color: #fff !important;
        background-color: #22AB38 !important;
    }

    .reward-tabs .alipay-tab .active {
        color: #fff !important;
        background-color: #019FE8 !important;
    }

    .reward-tabs .reward-img {
        width: 210px;
        height: 210px;
    }
</style>

<div id="reward">
    <a href="#rewardModal" class="reward-link modal-trigger btn-floating btn-medium waves-effect waves-light red">赏</a>

    <!-- Modal Structure -->
    <div id="rewardModal" class="modal">
        <div class="modal-content">
            <a class="close modal-close"><i class="fas fa-times"></i></a>
            <h4 class="reward-title">你的赏识是我前进的动力</h4>
            <div class="reward-content">
                <div class="reward-tabs">
                    <ul class="tabs row">
                        <li class="tab col s6 alipay-tab waves-effect waves-light"><a href="#alipay">支付宝</a></li>
                        <li class="tab col s6 wechat-tab waves-effect waves-light"><a href="#wechat">微 信</a></li>
                    </ul>
                    <div id="alipay">
                        <img src="/medias/reward/alipay.png" class="reward-img" alt="支付宝打赏二维码">
                    </div>
                    <div id="wechat">
                        <img src="/medias/reward/wechat.jpg" class="reward-img" alt="微信打赏二维码">
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    $(function () {
        $('.tabs').tabs();
    });
</script>

            
        </div>
    </div>

    

    

    

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/2022/11/18/shell-chu-li-wen-jian-lu-jing/">
                    <div class="card-image">
                        
                        <img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221118162836713.png" class="responsive-img" alt="Shell处理文件路径">
                        
                        <span class="card-title">Shell处理文件路径</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            本文主要介绍了Shell脚本处理文本路径的一系列方法
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2022-11-18
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/Linux%E5%AE%9E%E7%94%A8%E6%8A%80%E5%B7%A7/" class="post-category">
                                    Linux实用技巧
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/Linux/">
                        <span class="chip bg-color">Linux</span>
                    </a>
                    
                    <a href="/tags/Ubuntu/">
                        <span class="chip bg-color">Ubuntu</span>
                    </a>
                    
                    <a href="/tags/Shell/">
                        <span class="chip bg-color">Shell</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/2022/11/13/suo-ji-qi-shi-xian-hu-chi-suo-tiao-jian-bian-liang-yi-ji-xin-hao-liang/">
                    <div class="card-image">
                        
                        <img src="https://jack-1307599355.cos.ap-shanghai.myqcloud.com/image-20221113224353603.png" class="responsive-img" alt="锁及其实现：自旋锁、互斥锁、条件锁（条件变量）以及信号量">
                        
                        <span class="card-title">锁及其实现：自旋锁、互斥锁、条件锁（条件变量）以及信号量</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            本文介绍了各种锁：自旋锁、互斥锁、条件锁、信号量及其实现
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2022-11-13
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/" class="post-category">
                                    操作系统
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/Operating-System/">
                        <span class="chip bg-color">Operating System</span>
                    </a>
                    
                    <a href="/tags/%E9%94%81/">
                        <span class="chip bg-color">锁</span>
                    </a>
                    
                    <a href="/tags/%E4%BF%A1%E5%8F%B7%E9%87%8F/">
                        <span class="chip bg-color">信号量</span>
                    </a>
                    
                    <a href="/tags/%E4%BA%92%E6%96%A5%E9%94%81/">
                        <span class="chip bg-color">互斥锁</span>
                    </a>
                    
                    <a href="/tags/%E8%87%AA%E6%97%8B%E9%94%81/">
                        <span class="chip bg-color">自旋锁</span>
                    </a>
                    
                    <a href="/tags/%E6%9D%A1%E4%BB%B6%E9%94%81/">
                        <span class="chip bg-color">条件锁</span>
                    </a>
                    
                    <a href="/tags/%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F/">
                        <span class="chip bg-color">条件变量</span>
                    </a>
                    
                    <a href="/tags/%E9%94%81%E7%9A%84%E5%AE%9E%E7%8E%B0/">
                        <span class="chip bg-color">锁的实现</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>


<script>
    $('#articleContent').on('copy', function (e) {
        // IE8 or earlier browser is 'undefined'
        if (typeof window.getSelection === 'undefined') return;

        var selection = window.getSelection();
        // if the selection is short let's not annoy our users.
        if (('' + selection).length < Number.parseInt('120')) {
            return;
        }

        // create a div outside of the visible area and fill it with the selected text.
        var bodyElement = document.getElementsByTagName('body')[0];
        var newdiv = document.createElement('div');
        newdiv.style.position = 'absolute';
        newdiv.style.left = '-99999px';
        bodyElement.appendChild(newdiv);
        newdiv.appendChild(selection.getRangeAt(0).cloneContents());

        // we need a <pre> tag workaround.
        // otherwise the text inside "pre" loses all the line breaks!
        if (selection.getRangeAt(0).commonAncestorContainer.nodeName === 'PRE' || selection.getRangeAt(0).commonAncestorContainer.nodeName === 'CODE') {
            newdiv.innerHTML = "<pre>" + newdiv.innerHTML + "</pre>";
        }

        var url = document.location.href;
        newdiv.innerHTML += '<br />'
            + '来源: JackWang&#39;s Blog<br />'
            + '文章作者: Jack Wang<br />'
            + '文章链接: <a href="' + url + '">' + url + '</a><br />'
            + '本文章著作权归作者所有，任何形式的转载都请注明出处。';

        selection.selectAllChildren(newdiv);
        window.setTimeout(function () {bodyElement.removeChild(newdiv);}, 200);
    });
</script>


<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/libs/codeBlock/codeShrink.js"></script>


    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget card" style="background-color: white;">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('2'),
            headingSelector: 'h1, h2, h3, h4, h5, h6'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h1, h2, h3, h4, h5, h6').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>


<script src="https://cdn.bootcss.com/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script>
    MathJax.Hub.Config({
        tex2jax: {inlineMath: [['$', '$'], ['\\(', '\\)']]}
    });
</script>



    <footer class="page-footer bg-color">
    

    <div class="container row center-align"
         style="margin-bottom: 15px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            
                <span id="year">2021-2023</span>
            
            <a href="/about" target="_blank">Jack Wang</a>
            <!-- |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a> -->
            <!-- |&nbsp;Theme&nbsp;<a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a> -->
            <br>
            
                &nbsp;<i class="fas fa-chart-area"></i>&nbsp;站点总字数:&nbsp;<span
                        class="white-color">603.8k</span>
            
            
            
                
            
            
                <span id="busuanzi_container_site_pv">
                &nbsp;|&nbsp;<i class="far fa-eye"></i>&nbsp;总访问量:&nbsp;
                    <span id="busuanzi_value_site_pv" class="white-color"></span>
            </span>
            
            
                <span id="busuanzi_container_site_uv">
                &nbsp;|&nbsp;<i class="fas fa-users"></i>&nbsp;总访问人数:&nbsp;
                    <span id="busuanzi_value_site_uv" class="white-color"></span>
            </span>
            
            <br>

            <!-- 运行天数提醒. -->
            
                <span id="sitetime"> Loading ...</span>
                <script>
                    var calcSiteTime = function () {
                        var seconds = 1000;
                        var minutes = seconds * 60;
                        var hours = minutes * 60;
                        var days = hours * 24;
                        var years = days * 365;
                        var today = new Date();
                        var startYear = "2021";
                        var startMonth = "11";
                        var startDate = "12";
                        var startHour = "0";
                        var startMinute = "0";
                        var startSecond = "0";
                        var todayYear = today.getFullYear();
                        var todayMonth = today.getMonth() + 1;
                        var todayDate = today.getDate();
                        var todayHour = today.getHours();
                        var todayMinute = today.getMinutes();
                        var todaySecond = today.getSeconds();
                        var t1 = Date.UTC(startYear, startMonth, startDate, startHour, startMinute, startSecond);
                        var t2 = Date.UTC(todayYear, todayMonth, todayDate, todayHour, todayMinute, todaySecond);
                        var diff = t2 - t1;
                        var diffYears = Math.floor(diff / years);
                        var diffDays = Math.floor((diff / days) - diffYears * 365);

                        // 区分是否有年份.
                        var language = 'zh-CN';
                        if (startYear === String(todayYear)) {
                            document.getElementById("year").innerHTML = todayYear;
                            var daysTip = 'This site has been running for ' + diffDays + ' days';
                            if (language === 'zh-CN') {
                                daysTip = '本站已运行 ' + diffDays + ' 天';
                            } else if (language === 'zh-HK') {
                                daysTip = '本站已運行 ' + diffDays + ' 天';
                            }
                            document.getElementById("sitetime").innerHTML = daysTip;
                        } else {
                            document.getElementById("year").innerHTML = startYear + " - " + todayYear;
                            var yearsAndDaysTip = 'This site has been running for ' + diffYears + ' years and '
                                + diffDays + ' days';
                            if (language === 'zh-CN') {
                                yearsAndDaysTip = '本站已运行 ' + diffYears + ' 年 ' + diffDays + ' 天';
                            } else if (language === 'zh-HK') {
                                yearsAndDaysTip = '本站已運行 ' + diffYears + ' 年 ' + diffDays + ' 天';
                            }
                            document.getElementById("sitetime").innerHTML = yearsAndDaysTip;
                        }
                    }

                    calcSiteTime();
                </script>
            
            <br>
            
                <span id="icp"><img src="/medias/icp.png"
                                    style="vertical-align: text-bottom;"/>
                <a href="https://beian.miit.gov.cn" target="_blank">陕ICP备2021014294号-1</a>
            </span>
            
        </div>
        <div class="col s12 m4 l4 social-link social-statis">
    <a href="https://github.com/jackwang0108" class="tooltipped" target="_blank" data-tooltip="访问我的GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>



    <a href="mailto:2232123545@qq.com" class="tooltipped" target="_blank" data-tooltip="邮件联系我" data-position="top" data-delay="50">
        <i class="fas fa-envelope-open"></i>
    </a>







    <a href="tencent://AddContact/?fromId=50&fromSubId=1&subcmd=all&uin=2232123545" class="tooltipped" target="_blank" data-tooltip="QQ联系我: 2232123545" data-position="top" data-delay="50">
        <i class="fab fa-qq"></i>
    </a>







</div>
    </div>
</footer>

<div class="progress-bar"></div>


    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var searchFunc = function (path, search_id, content_id) {
        'use strict';
        $.ajax({
            url: path,
            dataType: "xml",
            success: function (xmlResponse) {
                // get the contents from search data
                var datas = $("entry", xmlResponse).map(function () {
                    return {
                        title: $("title", this).text(),
                        content: $("content", this).text(),
                        url: $("url", this).text()
                    };
                }).get();
                var $input = document.getElementById(search_id);
                var $resultContent = document.getElementById(content_id);
                $input.addEventListener('input', function () {
                    var str = '<ul class=\"search-result-list\">';
                    var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                    $resultContent.innerHTML = "";
                    if (this.value.trim().length <= 0) {
                        return;
                    }
                    // perform local searching
                    datas.forEach(function (data) {
                        var isMatch = true;
                        var data_title = data.title.trim().toLowerCase();
                        var data_content = data.content.trim().replace(/<[^>]+>/g, "").toLowerCase();
                        var data_url = data.url;
                        data_url = data_url.indexOf('/') === 0 ? data.url : '/' + data_url;
                        var index_title = -1;
                        var index_content = -1;
                        var first_occur = -1;
                        // only match artiles with not empty titles and contents
                        if (data_title !== '' && data_content !== '') {
                            keywords.forEach(function (keyword, i) {
                                index_title = data_title.indexOf(keyword);
                                index_content = data_content.indexOf(keyword);
                                if (index_title < 0 && index_content < 0) {
                                    isMatch = false;
                                } else {
                                    if (index_content < 0) {
                                        index_content = 0;
                                    }
                                    if (i === 0) {
                                        first_occur = index_content;
                                    }
                                }
                            });
                        }
                        // show search results
                        if (isMatch) {
                            str += "<li><a href='" + data_url + "' class='search-result-title'>" + data_title + "</a>";
                            var content = data.content.trim().replace(/<[^>]+>/g, "");
                            if (first_occur >= 0) {
                                // cut out 100 characters
                                var start = first_occur - 20;
                                var end = first_occur + 80;
                                if (start < 0) {
                                    start = 0;
                                }
                                if (start === 0) {
                                    end = 100;
                                }
                                if (end > content.length) {
                                    end = content.length;
                                }
                                var match_content = content.substr(start, end);
                                // highlight all keywords
                                keywords.forEach(function (keyword) {
                                    var regS = new RegExp(keyword, "gi");
                                    match_content = match_content.replace(regS, "<em class=\"search-keyword\">" + keyword + "</em>");
                                });

                                str += "<p class=\"search-result\">" + match_content + "...</p>"
                            }
                            str += "</li>";
                        }
                    });
                    str += "</ul>";
                    $resultContent.innerHTML = str;
                });
            }
        });
    };

    searchFunc('/search.xml', 'searchInput', 'searchResult');
});
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="/libs/materialize/materialize.min.js"></script>
    <script src="/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/libs/aos/aos.js"></script>
    <script src="/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/js/matery.js"></script>

    

    
        
        <script type="text/javascript">
            // 只在桌面版网页启用特效
            var windowWidth = $(window).width();
            if (windowWidth > 768) {
                document.write('<script type="text/javascript" src="/libs/others/sakura.js"><\/script>');
            }
        </script>
    

    <!-- 雪花特效 -->
    

    <!-- 鼠标星星特效 -->
    

     
        <script src="https://ssl.captcha.qq.com/TCaptcha.js"></script>
        <script src="/libs/others/TencentCaptcha.js"></script>
        <button id="TencentCaptcha" data-appid="xxxxxxxxxx" data-cbfn="callback" type="button" hidden></button>
    

    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    <script src="/libs/others/clicklove.js" async="async"></script>
    
    
    <script async src="/libs/others/busuanzi.pure.mini.js"></script>
    

    

    

    <!--腾讯兔小巢-->
    
    

    

    

    
    <script src="/libs/instantpage/instantpage.js" type="module"></script>
    

</body>

</html>
